<?php

declare(strict_types=1);
/**
 * This file is part of MoChat.
 * @link     https://mo.chat
 * @document https://mochat.wiki
 * @contact  group@mo.chat
 * @license  https://github.com/mochat-cloud/mochat/blob/master/LICENSE
 */
namespace MoChat\App\WorkEmployee\Logic;

use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Utils\Codec\Json;
use MoChat\App\Corp\Contract\CorpContract;
use MoChat\App\Corp\Utils\WeWorkFactory;
use MoChat\App\User\Contract\UserContract;
use MoChat\App\Utils\Str;
use MoChat\App\WorkContact\Queue\SyncContactQueue;
use MoChat\App\WorkDepartment\Contract\WorkDepartmentContract;
use MoChat\App\WorkEmployee\Constants\ContactAuth;
use MoChat\App\WorkEmployee\Contract\WorkEmployeeContract;
use MoChat\App\WorkEmployee\Contract\WorkEmployeeDepartmentContract;
use Psr\SimpleCache\CacheInterface;

/**
 * Class SyncEmployeeDetailLogic.
 */
class SyncEmployeeDetailLogic
{
    /**
     * 是否正在同步某企业客户.
     */
    public const LAST_SYNC_CONTACT_TIME = 'sync_contact:%s';

    /**
     * @Inject
     * @var WorkDepartmentContract
     */
    protected $workDepartmentService;

    /**
     * @Inject
     * @var CorpContract
     */
    protected $corpService;

    /**
     * @var StdoutLoggerInterface
     */
    protected $logger;

    /**
     * @Inject
     * @var WeWorkFactory
     */
    protected $weWorkFactory;

    /**
     * @Inject
     * @var SyncContactQueue
     */
    protected $syncContactQueue;

    /**
     * @Inject
     * @var WorkEmployeeContract
     */
    protected $workEmployeeService;

    /**
     * @Inject
     * @var WorkEmployeeDepartmentContract
     */
    protected $workEmployeeDepartmentService;

    /**
     * @Inject
     * @var UserContract
     */
    protected $userService;

    /**
     * @Inject
     * @var CacheInterface
     */
    protected $cache;

    public function handle(int $corpId, string $wxUserId, int $lastEmployee)
    {
        $loggerFactory = make(LoggerFactory::class);
        $this->logger = $loggerFactory->get(__CLASS__);
        $this->logger->debug(sprintf('同步员工详情逻辑开始, 企业ID:%s, 员工ID:%s', (string) $corpId, (string) $wxUserId));

        $wxEmployee = $this->getEmployeeDetail($corpId, $wxUserId);
        if (empty($wxEmployee)) {
            return;
        }

        $this->createOrUpdateEmployee($corpId, $wxEmployee);
        $this->checkAndStartSyncContact($corpId, $lastEmployee);
    }

    /**
     * 获取员工详情.
     */
    private function getEmployeeDetail(int $corpId, string $wxUserId): array
    {
        $weWorkApp = $this->weWorkFactory->getUserApp($corpId);
        $wxResult = $weWorkApp->user->get($wxUserId);

        if ($wxResult['errcode'] !== 0) {
            $this->logger->error(sprintf('获取员工详情出错，企业ID:%s，员工ID:%s，错误信息：%s', (string) $corpId, (string) $wxUserId, $wxResult['errmsg']));
            return [];
        }

        return $wxResult;
    }

    /**
     * 创建或更新员工.
     *
     * @param $wxEmployee
     * @return int
     */
    private function createOrUpdateEmployee(int $corpId, $wxEmployee)
    {
        if ($employee = $this->isExistsEmployee($corpId, $wxEmployee)) {
            return $this->updateEmployee($corpId, $wxEmployee, $employee);
        }
        return $this->createEmployee($corpId, $wxEmployee);
    }

    /**
     * 判断员工是否存在.
     */
    private function isExistsEmployee(int $corpId, array $wxEmployee): array
    {
        return $this->workEmployeeService->getWorkEmployeeByCorpIdAndWxUserId($corpId, $wxEmployee['userid']);
    }

    /**
     * 创建员工.
     */
    private function createEmployee(int $corpId, array $wxEmployee): int
    {
        $wxDepartments = $this->getEmployeeDepartmentIdList($corpId, $wxEmployee['department']);
        $createData = $this->formatData($corpId, $wxEmployee, $wxDepartments);
        $employeeId = $this->workEmployeeService->createWorkEmployee($createData);

        if (! empty($wxEmployee['department'])) {
            foreach ($wxEmployee['department'] as $key => $value) {
                $createEmployeeDepartmentData = [
                    'employee_id' => $employeeId,
                    'department_id' => $wxDepartments[$value]['id'],
                    'is_leader_in_dept' => $wxEmployee['is_leader_in_dept'][$key],
                    'order' => $key,
                    'created_at' => date('Y-m-d H:i:s'),
                ];
                $this->workEmployeeDepartmentService->createWorkEmployeeDepartment($createEmployeeDepartmentData);
            }
        }

        return $employeeId;
    }

    /**
     * 更新部门.
     *
     * @param array $wxDepartment
     */
    private function updateEmployee(int $corpId, array $wxEmployee, array $employee): int
    {
        $wxDepartments = $this->getEmployeeDepartmentIdList($corpId, $wxEmployee['department']);
        $departmentIds = array_column($wxDepartments, 'id');
        $updateData = $this->formatData($corpId, $wxEmployee, $wxDepartments, false);
        $this->workEmployeeService->updateWorkEmployeeById($employee['id'], $updateData);

        // 员工与部门关系更新
        $localEmployeeDepartment = $this->workEmployeeDepartmentService->getWorkEmployeeDepartmentsByEmployeeId($employee['id']);

        $localEmployeeDepartment = array_column($localEmployeeDepartment, null, 'departmentId');
        $localEmployeeDepartmentIds = array_column($localEmployeeDepartment, 'departmentId');
        if (! empty($localEmployeeDepartment)) {
            foreach ($localEmployeeDepartment as $key => $value) {
                // 已删除
                if (! in_array($value['departmentId'], $departmentIds)) {
                    $this->workEmployeeDepartmentService->deleteWorkEmployeeDepartment($value['id']);
                }
            }
        }

        // 新增
        if (! empty($wxDepartments)) {
            foreach ($wxDepartments as $key => $value) {
                if (in_array($value['id'], $localEmployeeDepartmentIds)) {
                    continue;
                }

                $index = array_search($value['wxDepartmentId'], $wxEmployee['department']);
                $isLeader = 0;
                if (! empty($wxEmployee['is_leader_in_dept'][$index]) && $wxEmployee['is_leader_in_dept'][$index]) {
                    $isLeader = 1;
                }

                $createEmployeeDepartmentData = [
                    'employee_id' => $employee['id'],
                    'department_id' => $value['id'],
                    'is_leader_in_dept' => $isLeader,
                    'order' => $index,
                    'created_at' => date('Y-m-d H:i:s'),
                ];
                $this->workEmployeeDepartmentService->createWorkEmployeeDepartment($createEmployeeDepartmentData);
            }
        }

        return $employee['id'];
    }

    /**
     * 获取用户id.
     *
     * @return int|mixed
     */
    private function getUserId(int $corpId, array $wxEmployee)
    {
        $wxEmployee['mobile'] = ! empty($wxEmployee['mobile']) ? $wxEmployee['mobile'] : '40412345678';
        $userInfo = $wxEmployee['mobile'] === '40412345678' ? [] : $this->userService->getUserByPhone($wxEmployee['mobile'], ['id']);
        if (! empty($userInfo)) {
            return $userInfo['id'];
        }

        return $this->createUser($corpId, $wxEmployee);
    }

    /**
     * 创建用户.
     *
     * @return int
     */
    private function createUser(int $corpId, array $wxEmployee)
    {
        // 租户id
        $tenant = $this->corpService->getCorpById($corpId, ['tenant_id']);

        $data = [
            'phone' => $wxEmployee['mobile'],
            'password' => Str::generateSignaturePassword(),
            'name' => $wxEmployee['name'] ?? '',
            'gender' => $wxEmployee['gender'] ?? 0,
            'department' => $wxEmployee['department_id'] ?? 0,
            'position' => $wxEmployee['position'] ?? '',
            'tenant_id' => $tenant['tenantId'],
            'created_at' => date('Y-m-d H:i:s'),
        ];

        return $this->userService->createUser($data);
    }

    /**
     * 获取员工部门id.
     *
     * @return array
     */
    private function getEmployeeDepartmentIdList(int $corpId, array $wxDepartmentIdList)
    {
        $department = $this->workDepartmentService->getWorkDepartmentsByCorpIdWxDepartmentIds($corpId, $wxDepartmentIdList, ['id', 'wx_department_id']);
        if (empty($department)) {
            return $department;
        }

        return (array) array_column($department, null, 'wxDepartmentId');
    }

    /**
     * 格式化数据.
     *
     * @param bool $isCreate
     */
    private function formatData(int $corpId, array $wxEmployee, array $departments, $isCreate = true): array
    {
        $data = [
            'wx_user_id' => $wxEmployee['userid'],
            'corp_id' => $corpId,
            'name' => $wxEmployee['name'],
            'mobile' => $wxEmployee['mobile'] ?? '',
            'position' => $wxEmployee['position'],
            'gender' => $wxEmployee['gender'] ?? 0,
            'email' => $wxEmployee['email'] ?? '',
            'avatar' => $wxEmployee['avatar'] ?? '',
            'thumb_avatar' => $wxEmployee['thumb_avatar'] ?? '',
            'telephone' => $wxEmployee['telephone'] ?? '',
            'alias' => $wxEmployee['alias'] ?? '',
            'extattr' => ! empty($wxEmployee['extattr']) ? Json::encode($wxEmployee['extattr']) : Json::encode([]),
            'status' => $wxEmployee['status'],
            'qr_code' => $wxEmployee['qr_code'] ?? '',
            'external_profile' => ! empty($wxEmployee['external_profile']) ? Json::encode($wxEmployee['external_profile']) : Json::encode([]),
            'external_position' => ! empty($wxEmployee['external_position']) ? Json::encode($wxEmployee['external_position']) : Json::encode([]),
            'address' => ! empty($wxEmployee['address']) ? $wxEmployee['address'] : '',
            'open_user_id' => ! empty($wxEmployee['open_userid']) ? $wxEmployee['open_userid'] : 0,
            'wx_main_department_id' => isset($wxEmployee['main_department']) && ! empty($wxEmployee['main_department']) ? $wxEmployee['main_department'] : 0,
            'main_department_id' => isset($wxEmployee['main_department']) && ! empty($wxEmployee['main_department']) && ! empty($departments[$wxEmployee['main_department']]) ? $departments[$wxEmployee['main_department']]['id'] : 0,
            'contact_auth' => ContactAuth::NO,
        ];

        if ($isCreate) {
            $data['log_user_id'] = $this->getUserId($corpId, $wxEmployee);
            $data['created_at'] = date('Y-m-d H:i:s');
        }

        return $data;
    }

    /**
     * 检测员工同步进度并开始同步客户.
     */
    private function checkAndStartSyncContact(int $corpId, int $lastEmployee)
    {
        // 是否为最后一个员工
        if ($lastEmployee !== 1) {
            return;
        }

        if (! $this->allowSyncContact($corpId)) {
            return;
        }

        // 最后一个员工同步完成，30秒后开始同步客户，防止队列并发执行时最后一个不准确问题
        $this->syncContactQueue->syncContactByCorpId([
            'corpId' => $corpId,
        ], 30);

        $this->setSyncContactTime($corpId);
    }

    /**
     * 30分钟允许同步一次
     *
     * @return bool
     */
    private function allowSyncContact(int $corpId)
    {
        $lastSyncContactTime = $this->cache->get(sprintf(self::LAST_SYNC_CONTACT_TIME, (string) $corpId));
        if (empty($lastSyncContactTime)) {
            return true;
        }

        if (time() - $lastSyncContactTime > 1800) {
            return true;
        }

        return false;
    }

    private function setSyncContactTime(int $corpId)
    {
        $this->cache->set(sprintf(self::LAST_SYNC_CONTACT_TIME, (string) $corpId), time(), 60 * 30);
    }
}
